// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file is part of CodingEditor.
// Note:	This project is derived from Peter Project
//			(hosted on sourceforge and codeplex)
//
// Copyright (c) 2008-2009, CE Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


namespace CE.Main
{
	using System;
	using System.Collections;
	using System.ComponentModel;
	using System.IO;
	using System.Text.RegularExpressions;
	using System.Threading;
	using System.Windows.Forms;

	using CE.API;
	using CE.Classes;

	using WeifenLuo.WinFormsUI.Docking;

	#region Delegates

	public delegate void AddTreeNodeDelegate(TreeNode node);

	public delegate void TraceDelegate(string text);

	#endregion Delegates

	public sealed partial class Find : CEBaseForm
	{
		#region Fields

		private readonly AddTreeNodeDelegate m_AddTreeNode;
		private readonly ArrayList m_FindLocations;
		//private readonly MainForm _parent;
		private readonly FindResults m_Results;
		private readonly TraceDelegate m_Trace;

		private const string BROWSE = "Browse...";
		private const string OpenDoc = "Open Documents";

		private ICEPluginHost m_Host;
		private Thread m_thrdFind;

		#endregion Fields

		#region Constructors

		public Find(MainForm parent) : base(parent)
		{
			InitializeComponent();

			//_parent = main;
			m_Trace = Trace;
			m_AddTreeNode = AddNode;
			m_FindLocations = new ArrayList();
			m_Results = new FindResults();
			m_Results.Tree.DoubleClick += Tree_DoubleClick;
			cmbFindText.KeyDown += cmbFindText_KeyDown;
		}

		#endregion Constructors

		#region Properties

		/// <summary>
		/// Gets or Sets the Text to Find...
		/// </summary>
		public string FindText
		{
			get { return cmbFindText.Text; }

			set { cmbFindText.Text = value; }
		}

		/// <summary>
		/// Gets if we should search up or not...
		/// </summary>
		public bool FindUp
		{
			get { return ckbSearchUp.Checked; }
		}

		/// <summary>
		/// Gets or Sets the Host Application...
		/// </summary>
		public ICEPluginHost Host
		{
			get { return m_Host; }

			set { m_Host = value; }
		}

		/// <summary>
		/// Gets the Text to Replace..
		/// </summary>
		public string ReplaceText
		{
			get { return cmbReplace.Text; }
		}

		/// <summary>
		/// Gets the Find Results DockControl...
		/// </summary>
		public FindResults Results
		{
			get { return m_Results; }
		}

		#endregion Properties

		#region Methods

		public void FindInFile(FileInfo file, string searchText, Regex reg)
		{
			Invoke(m_Trace, new object[] {"Searching " + file.FullName});
			var sr = new StreamReader(file.FullName);
			string line;
			int lineNum = 1;
			var nodeRoot = new TreeNode(file.Name) {
				ToolTipText = file.FullName
			};
			while ((line = sr.ReadLine()) != null) {
				Match m = reg.Match(line);
				if (m.Success) {
					var node = new TreeNode(lineNum + " - " + line.Replace("\t", " ").Trim()){
						Tag = new FindInfo(m.Index, lineNum, searchText, file.FullName)
					};
					nodeRoot.Nodes.Add(node);
				}
				lineNum++;
			}
			if (nodeRoot.Nodes.Count > 0) {
				Invoke(m_AddTreeNode, new object[] {nodeRoot});
			}
		}

		public void FindInFiles(object dirObj)
		{
			var info = (ArrayList) dirObj;
			var dirInfo = new DirectoryInfo(info[1].ToString());
			string[] filters = info[2].ToString().Split('|');
			foreach (string filter in filters) {
				if (filter.Trim() != string.Empty) {
					foreach (FileInfo file in dirInfo.GetFiles(filter.Trim(), SearchOption.AllDirectories)) {
						FindInFile(file, info[0].ToString(), (Regex) info[3]);
					}
				}
			}

			Invoke(m_Trace, new object[] {"Search Complete."});
		}

		/// <summary>
		/// Gets the Regular Expression to search for...
		/// </summary>
		/// <returns>RegEx</returns>
		public Regex GetRegEX()
		{
			string regExString = cmbFindText.Text;

			if (ckbUseRegEx.Checked) {
				// We dont need to do anything...
			} else if (ckbWildCard.Checked) {
				regExString = regExString.Replace("*", @"\w*");
				regExString = regExString.Replace("?", @"\w");

				regExString = String.Format("{0}{1}{0}", @"\b", regExString);
			} else {
				regExString = Regex.Escape(regExString);
			}

			if (ckbMatchWord.Checked) {
				regExString = String.Format("{0}{1}{0}", @"\b", regExString);
			}

			if (ckbMatchCase.Checked) {
				return new Regex(regExString);
			}
			return new Regex(regExString, RegexOptions.IgnoreCase);
		}

		public void SetFind(bool files)
		{
			ckbFindIn.Checked = files;
			tsbFind_Click(null, null);
		}

		public void SetReplace(bool files)
		{
			ckbFindIn.Checked = files;
			tsbReplace_Click(null, null);
		}

		protected override void OnClosing(CancelEventArgs e)
		{
			e.Cancel = true;
			Hide();
			base.OnClosing(e);
		}

		private void AddNode(TreeNode node)
		{
			string file = node.ToolTipText;
			string ext = Path.GetExtension(file);
			if (ext.Trim() == string.Empty) {
				ext = "none";
			}
			if (!m_Results.Images.Images.ContainsKey(ext)) {
				m_Results.Images.Images.Add(ext, Common.GetFileIcon(file, false).ToBitmap());
			}

			node.ImageIndex = node.SelectedImageIndex = m_Results.Images.Images.IndexOfKey(ext);

			m_Results.Tree.Nodes.Add(node);
			node.Expand();

			// Update count...
			if (m_Results.TabText.IndexOf("-") != -1) {
				string[] txt = m_Results.TabText.Split('-');
				string matchCnt = txt[1].Trim();
				matchCnt = matchCnt.Split(' ')[0].Trim();
				matchCnt = Convert.ToString(Convert.ToInt32(matchCnt) + node.Nodes.Count);
				m_Results.TabText = "Find Results - " + matchCnt;
				m_Results.TabText += (matchCnt.Equals("1")) ? " result" : " results";
			} else {
				m_Results.TabText += " - " + node.Nodes.Count;
				m_Results.TabText += (node.Nodes.Count == 1) ? " result" : " results";
			}
		}

		private void Find_Activated(object sender, EventArgs e)
		{
			Opacity = 1;
		}

		private void Find_Deactivate(object sender, EventArgs e)
		{
			Opacity = 0.7;
		}

		private void Find_FormClosing(object sender, FormClosingEventArgs e)
		{
			Dispose(true);
		}

		private void Find_Paint(object sender, PaintEventArgs e)
		{
			Program.MakeBackgroundGradient(e.Graphics, this);
		}

		private void Trace(string text)
		{
			if (text.Equals("Search Complete.")) {
				btnStop.Visible = false;
			}
			m_Host.ChangeStatus(text);
		}

		private void Tree_DoubleClick(object sender, EventArgs e)
		{
			if (m_Results.Tree.SelectedNode != null) {
				var fi = (FindInfo) m_Results.Tree.SelectedNode.Tag;
				string tabName = Path.GetFileName(fi.FilePath);
				string filePath = fi.FilePath;
				ParentForm.CreateEditor( filePath,  tabName);
				ParentForm.SelectWord(fi.Line - 1, fi.Index, fi.FindString.Length);
			}
		}

		private void UpdateComboBoxes()
		{
			if (!cmbFindText.Items.Contains(cmbFindText.Text)) {
				cmbFindText.Items.Add(cmbFindText.Text);
			}
			if (cmbReplace.Enabled) {
				if (!cmbReplace.Items.Contains(cmbReplace.Text)) {
					cmbReplace.Items.Add(cmbReplace.Text);
				}
			}
			if (cmbFilter.Enabled) {
				if (!cmbFilter.Items.Contains(cmbFilter.Text)) {
					cmbFilter.Items.Add(cmbFilter.Text);
				}
			}
			if (cmbFindIn.Enabled) {
				if (!cmbFindIn.Items.Contains(cmbFindIn.Text)) {
					cmbFindIn.Items.Add(cmbFindIn.Text);
				}
				if (!m_FindLocations.Contains(cmbFindIn.Text)) {
					m_FindLocations.Add(cmbFindIn.Text);
				}
			}
		}

		private void btnFind_Click(object sender, EventArgs e)
		{
            if (ParentForm.ActiveEditor != null) {
			    UpdateComboBoxes();
			    if (tsbSearch.Checked) {
				    if (!ckbFindIn.Checked) {
					    // Find Next...
					    ParentForm.FindNext(FindUp);
				    }
			    } else {
				    if (!ckbFindIn.Checked) {
					    // Replace Next..
					    ParentForm.ReplaceNext();
				    }
			    }
            }
		}

		private void btnMark_Click(object sender, EventArgs e)
		{
			UpdateComboBoxes();
			if (tsbSearch.Checked) {
				if (ckbFindIn.Checked) {
					m_Results.Tree.Nodes.Clear();
					m_Results.TabText = "Find Results";
					if (m_Results.DockState == DockState.Unknown) {
						DockContent mResults = m_Results;
						m_Host.AddDockContent( mResults, DockState.DockBottom);
					} else if (m_Results.DockState == DockState.Hidden) {
						m_Results.Show();
					}
					// Find All in Files...
					if (cmbFindIn.Text == OpenDoc) {
						ParentForm.FindInOpenFiles();
					} else {
						ParentForm.ChangeStatus("Building File List...");
						var info = new ArrayList {cmbFindText.Text, cmbFindIn.Text, cmbFilter.Text, GetRegEX()};
						btnStop.Visible = true;

						if (m_thrdFind != null)
							if (m_thrdFind.IsAlive)
								m_thrdFind.Abort();

						m_thrdFind = null;
						m_thrdFind = new Thread(FindInFiles);
						m_thrdFind.Start(info);
					}
				} else {
					// Mark All...
					ParentForm.MarkAll();
				}
			} else {
				if (!ckbFindIn.Checked) {
					// Replace All..
					ParentForm.ReplaceAll();
				} else {
					// Replace All in Files...
					if (cmbFindIn.Text == OpenDoc) {
						ParentForm.ReplaceInOpenFiles();
					}
				}
			}
		}

		private void btnStop_Click(object sender, EventArgs e)
		{
			if (m_thrdFind.IsAlive) {
				btnStop.Visible = false;
				m_thrdFind.Abort();
				m_thrdFind = null;
				Application.DoEvents();
				Trace("Search Aborted...");
			}
		}

		private void ckbFindIn_CheckedChanged(object sender, EventArgs e)
		{
			if (tsbSearch.Checked) {
				if (ckbFindIn.Checked) {
					cmbFindIn.Enabled = true;
					btnMark.Text = "Find All";
					btnFind.Enabled = false;
					lblFilter.Enabled = true;
					cmbFilter.Enabled = true;
				} else {
					cmbFindIn.Enabled = false;
					btnMark.Text = "Mark All";
					btnFind.Enabled = true;
					lblFilter.Enabled = false;
					cmbFilter.Enabled = false;
				}
			} else {
				if (ckbFindIn.Checked) {
					cmbFindIn.Enabled = true;
					btnFind.Enabled = false;
					lblFilter.Enabled = true;
					cmbFilter.Enabled = true;
				} else {
					cmbFindIn.Enabled = false;
					btnFind.Enabled = true;
					lblFilter.Enabled = false;
					cmbFilter.Enabled = false;
				}
			}
		}

		private void cmbFindIn_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (cmbFindIn.Text == BROWSE) {
				if (fbdMain.ShowDialog() == DialogResult.OK) {
					if (!cmbFindIn.Items.Contains(fbdMain.SelectedPath)) {
						cmbFindIn.Items.Add(fbdMain.SelectedPath);
						m_FindLocations.Add(fbdMain.SelectedPath);
					}
					cmbFindIn.Text = fbdMain.SelectedPath;
				} else {
					cmbFindIn.Text = OpenDoc;
				}
			}
		}

		private void cmbFindText_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Enter) {
				btnFind_Click(null, null);
			}
		}

		private void tsbFind_Click(object sender, EventArgs e)
		{
			if (!tsbSearch.Checked) {
				tsbSearch.Checked = true;
				tsbReplace.Checked = false;
				lblReplace.Enabled = false;
				cmbReplace.Enabled = false;
				btnFind.Text = "Find Next";
				btnMark.Text = "Mark All";

				string text = cmbFindIn.SelectedText;
				cmbFindIn.Items.Clear();
				cmbFindIn.Items.Add(OpenDoc);
				cmbFindIn.Items.Add(BROWSE);
				cmbFindIn.Text = OpenDoc;
				for (int a = 0; a < m_FindLocations.Count; a++) {
					cmbFindIn.Items.Add(m_FindLocations[a]);
				}
				cmbFindIn.Text = text;
				if (cmbFindIn.Text == string.Empty) {
					cmbFindIn.Text = OpenDoc;
				}

				ckbFindIn_CheckedChanged(null, null);
				cmbFindText.Focus();
			}
		}

		private void tsbReplace_Click(object sender, EventArgs e)
		{
			if (!tsbReplace.Checked) {
				tsbSearch.Checked = false;
				tsbReplace.Checked = true;
				lblReplace.Enabled = true;
				cmbReplace.Enabled = true;

				cmbFindIn.Items.Clear();
				cmbFindIn.Items.Add(OpenDoc);
				cmbFindIn.Text = OpenDoc;
				if (cmbFindIn.Text == string.Empty) {
					cmbFindIn.Text = OpenDoc;
				}

				btnFind.Text = "Replace Next";
				btnMark.Text = "Replace All";

				ckbFindIn_CheckedChanged(null, null);
				cmbReplace.Focus();
			}
		}

		#endregion Methods

		#region Nested Types

		private struct FindInfo
		{
			#region Fields

			public readonly string FilePath;
			public readonly string FindString;
			public readonly int Index;
			public readonly int Line;

			#endregion Fields

			#region Constructors

			public FindInfo(int index, int line, string find, string file)
			{
				FindString = find;
				Line = line;
				Index = index;
				FilePath = file;
			}

			#endregion Constructors

			public override int GetHashCode()
			{
				throw new NotImplementedException();
			}

			public override bool Equals(Object obj)
			{
				throw new NotImplementedException();
			}

			public static bool operator ==(FindInfo x, FindInfo y)
			{
				throw new NotImplementedException();
			}

			public static bool operator !=(FindInfo x, FindInfo y)
			{
				throw new NotImplementedException();
			}
		}

		#endregion Nested Types
	}
}